fix(vault): 保存凭据跳过未变化的 keychain 条目,修复切换供应商弹窗风暴 (#602)#634
Merged
Conversation
切换供应商只改 root.active 一个字段,但 save_credentials 会先读旧 manifest、 再重写所有 chunks、再重写 manifest —— macOS 上每个 keychain 条目各自 ACL, ad-hoc 签名/未永久授权时每次访问各弹一次「访问密码」弹窗,一次切换弹 3+ 次。 三处收口(都基于已有的进程级凭据缓存,行为冷路径不变): 1. manifest 进程缓存(CREDENTIALS_MANIFEST_CACHE):load/save 成功后回填, 后续 save 不再回 keychain 读旧 manifest(省 1 次 ACL 访问);冷缓存仍读 真实 manifest,UUID 代际旧 chunks 的清理信息不丢。 2. chunk_skip_mask:用缓存里上次落盘的 root 反推旧 chunk 内容,逐字节一致的 chunk 跳过重写。仅旧 manifest 已是稳定名(generation=None)时启用;缓存 序列化顺序差异只会多写(回退旧行为),不会漏写。 3. manifest 内容只由 chunks 数决定:数量不变且已是稳定名 → 跳过重写。 典型单 chunk 凭据库:切换供应商 3 次 keychain 访问 → 1 次。
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
H-Chris233
approved these changes
Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
User description
问题 (#602)
macOS 设置中切换供应商会连续弹出多次「OpenLess 想访问钥匙串」密码弹窗(v1.3.6-2 复现)。
根因:切换供应商只改
root.active一个字段,但save_credentials每次都执行读旧 manifest → 重写所有 chunks → 重写 manifest。macOS Keychain 每个条目各自 ACL,签名变化/未点「始终允许」时每次条目访问各弹一窗 → 一次切换 3+ 个弹窗。读路径早有进程缓存(PR #394),写路径没有。修复
基于已有的进程级凭据缓存做三处收口:
CREDENTIALS_MANIFEST_CACHE,后续 save 不再回 keychain 读旧 manifest。冷缓存仍读真实 manifest——UUID 代际旧 chunks 的清理信息不丢。chunk_skip_mask(纯函数,带单测):用缓存中上次落盘的 root 反推旧 chunk 内容,逐字节一致的 chunk 跳过重写。仅旧 manifest 已是稳定名(generation=None)时启用;任何不确定(冷缓存/序列化顺序差异/UUID 代际)都退回「全部重写」旧行为,只会多写不会漏写。效果:典型单 chunk 凭据库,切换供应商 keychain 访问 3 次 → 1 次;no-op 保存 0 次。保存时新增一行 info 日志(written/total + manifest 状态),方便用户日志回报验证。
安全性
验证
chunk_skip_mask_skips_unchanged_and_rewrites_changed(4 个场景)cargo test --lib:479 passed, 0 failedCloses #602
PR Type
Bug fix
Description
添加进程级 manifest 缓存,避免保存前读取钥匙串
实现 chunk_skip_mask 跳过未变化的 chunk
在 chunk 数量和 manifest 稳定时跳过 manifest 写入
添加相应的单元测试
Diagram Walkthrough
flowchart LR A["save_credentials"] --> B{"manifest cache hit & stable name?"} B -- "yes" --> C["chunk_skip_mask from cached root"] C --> D["write only changed chunks"] D --> E{"chunks count unchanged?"} E -- "yes" --> F["skip manifest write"] E -- "no" --> G["write new manifest"] B -- "no" --> H["read manifest from keychain"] H --> I["write all chunks"] I --> G F --> J["update caches"] G --> JFile Walkthrough
persistence.rs
添加 manifest 缓存和 chunk 跳过优化openless-all/app/src-tauri/src/persistence.rs